package cn.pkubangbang.demo.democontact.service.impl;

import static cn.pkubangbang.demo.democontact.entity.Role.ADMIN;
import static cn.pkubangbang.demo.democontact.entity.Role.ANIMAL;
import static cn.pkubangbang.demo.democontact.entity.Role.FOREIGNER;
import static cn.pkubangbang.demo.democontact.entity.Role.USER;

import cn.pkubangbang.demo.democontact.entity.Person;
import cn.pkubangbang.demo.democontact.entity.Role;
import cn.pkubangbang.demo.democontact.interceptor.TooFrequentRequestException;
import cn.pkubangbang.demo.democontact.service.AuthService;
import cn.pkubangbang.demo.democontact.service.impl.helper.Threshold;
import cn.pkubangbang.demo.democontact.service.impl.helper.ThresholdOverflowException;
import cn.pkubangbang.demo.democontact.service.impl.helper.TimeRewindingException;
import com.fasterxml.jackson.databind.ser.Serializers.Base;
import java.io.UnsupportedEncodingException;
import java.time.Instant;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.ibatis.jdbc.Null;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;

/**
 * @Author: zhangzhenbang@k2data.com.cn
 * @Date: 2018-08-24 16:16
 */
@Service
public class AuthServiceImpl implements AuthService {

    private final int TTL;

    public AuthServiceImpl(@Value("${custom.session.timeoutinseconds}") int ttl) {
        this.TTL = ttl;
    }

    private ConcurrentHashMap<String, Threshold> record = new ConcurrentHashMap<>();

    private static Logger logger = LoggerFactory.getLogger(AuthServiceImpl.class);

    @Override
    public boolean getPermission(String xHeader) {
        Threshold threshold = getThreshold(xHeader);
        if (threshold == null) {
            return false;
        }
        boolean timeout = threshold.getRecent() < System.currentTimeMillis() - TTL * 1000;
        if (timeout) {
            invalid(xHeader);
            return false;
        }
        return true;
    }

    @Override
    @Nullable
    public Threshold getThreshold(String xHeader) {
        if (xHeader == null || "".equals(xHeader)) {
            return null;
        }
        Threshold threshold = record.get(xHeader);
        logger.debug("looking for threashold of header {}: got {}", xHeader, threshold);
        return threshold;
    }

    @Override
    public void touch(String xHeader) throws TooFrequentRequestException {
        Threshold threshold = record.get(xHeader);
        // note 此处存在一些的不一致性，但是业务逻辑容许这些误差。
        if (threshold == null) {
            logger.debug("Threshold#touch(): header {} does not have threshold, ready to create one.", xHeader);
            // fixme make params variable
            Threshold newThreshold = new Threshold(5, 3000);
            try {
                newThreshold.push(System.currentTimeMillis());
            } catch (ThresholdOverflowException e) {
                throw new TooFrequentRequestException(e);
            } catch (TimeRewindingException e) {
                throw new IllegalStateException("It's unusual for a new Threshold to throw this Exception!", e);
            }
            record.put(xHeader, newThreshold);
        } else {
            logger.debug("Threshold#touch(): header {} is bound to threshold {}", xHeader, threshold);
            try {
                threshold.push(System.currentTimeMillis());
            } catch (ThresholdOverflowException e) {
                throw new TooFrequentRequestException();
            } catch (TimeRewindingException e) {
                threshold.clear();
                logger.warn("Server time has been reset! The threshold will be thus cleared.", e);
            }
        }
    }

    @Override
    public void invalid(String xHeader) {
        record.remove(xHeader);
    }

    @Override
    public Set<Role> getRoles(Person p) {
        switch (p.getName()) {
            case "熊大":
            case "熊二":
                return new HashSet<>(Arrays.asList(ANIMAL));
            case "张三":
                return new HashSet<>(Arrays.asList(ADMIN, USER));
            case "李四":
            case "王五":
            case "赵六":
            case "孙七":
                return new HashSet<>(Arrays.asList(USER));
            default:
                return new HashSet<>(Arrays.asList(FOREIGNER));
        }
    }

    @Override
    public String generateToken(Person p) throws UnsupportedEncodingException {
        // todo getName() may return Chinese chars, which are not suitable inside Http Headers.
        String token = p.getName() + "@" + Instant.now() + "#" + new Random().nextInt();
        // note to put string inside url-query, you need to Encode it using URLEncoder.
        return Base64.getEncoder().encodeToString(token.getBytes("utf-8"));
    }
}
